﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;
using System.Web.Compilation;
using System.Web.Hosting;
using System.Web.UI;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;
using dotNails.CodeGen.Models;
using dotNails.CodeGen.Models.dotNailsLinqToSqlGenerator;
using dotNails.CodeGen.Util;
using Microsoft.VisualStudio.Shell;
using VSLangProj80;

using CodeSmith.Engine;
using System.Diagnostics;

namespace dotNailsCustomTool {
	[ComVisible(true)]
	[Guid("49EE41BE-7E89-4c63-B700-2F3DECF256A8")]
	[CodeGeneratorRegistration(typeof(dotNailsLinqToSqlGenerator), "C# dotNails LINQ to SQL Class Generator", vsContextGuids.vsContextGuidVCSProject, GeneratesDesignTimeSource = true)]
	[ProvideObject(typeof(dotNailsLinqToSqlGenerator))]
	public class dotNailsLinqToSqlGenerator : BaseCodeGeneratorWithSite {
#pragma warning disable 0414
		//The name of this generator (use for 'Custom Tool' property of project item)
		internal static string name = "dotNailsLinqToSqlGenerator";
#pragma warning restore 0414

		/// <summary>
		/// Function that builds the contents of the generated file based on the contents of the input file
		/// </summary>
		/// <param name="inputFileContent">Content of the input file</param>
		/// <returns>Generated file as a byte array</returns>
		protected override byte[] GenerateCode(string inputFileContent) {
			try {
				// Comment this out for unit testing, or figure out how to simulate a shell site
				EnvDTE.Project project = GetProject();
				
				// Testing
				//Solution.Item(EnvDTE.Constants.vsSolutionItemsProjectUniqueName). 
				// Use the following to get to our config file.  It will reside in the same location as the solution
				string configFilePath = ConfigData.GetConfigFilePath(project.DTE.Solution.FileName);
				ConfigData configData = new ConfigData(configFilePath);

				string dotNailsCommonNamespace = GetDotNailsCommonNamespace(project);

				string defaultNamespace = "NamespaceUnknown";
				defaultNamespace = FileNameSpace;

				// Map the DBML XML to object model
				DbmlData dbmlData = new DbmlData(inputFileContent);

				if (this.CodeGeneratorProgress != null) {
					//Report that we are 1/2 done
					this.CodeGeneratorProgress.Progress(50, 100);
				}

				using (StringWriter writer = new StringWriter(new StringBuilder())) {
					//Generate the code
					StringBuilder output = new StringBuilder();
					Assembly assembly = this.GetType().Module.Assembly; // needed to retrieve embedded resources
					
					// Render the top of the DBMLDesigner file
					string dbmlDesignerTop = TemplateUtil.TransformSimpleTemplateToString("dotNailsCustomTool.Templates", "DbmlDesignerTop.cst",
															new Dictionary<string, string> { { "$Namespace$", defaultNamespace }, { "$dotNailsCommonNamespace$", dotNailsCommonNamespace } },
															assembly);
					output.Append(dbmlDesignerTop);

					// Render DataContext class
					DataContextClass dataContextClass = new DataContextClass(dbmlData.DB);
					//LogUtil.EventLogMessage("Rendering DataContextClass");
					output.Append(dataContextClass.Render());

					// Render table classes
					foreach (Table table in dbmlData.DB.Tables) {
						// Render table class
						TableClass tableClass = new TableClass(table, dbmlData.DB.Class, configData);
						//LogUtil.EventLogMessage("Rendering TableClass - " + table.Name);
						output.Append(tableClass.Render());
					}
					
					// Render proc results classes
					ProcResultsClass procResults = new ProcResultsClass(dbmlData.DB.Functions);
					output.Append(procResults.Render());

					// Render the bottom of the DBMLDesigner file
					string dbmlDesignerBottom = TemplateUtil.TransformSimpleTemplateToString("dotNailsCustomTool.Templates", "DbmlDesignerBottom.cst",
															new Dictionary<string, string> { { "$Namespace$", defaultNamespace } },
															assembly);
					
					output.Append(dbmlDesignerBottom);

					writer.WriteLine(output.ToString());

					if (this.CodeGeneratorProgress != null) {
						// Report that we are done
						this.CodeGeneratorProgress.Progress(100, 100);
					}
					writer.Flush();

					// Is all of this stuff needed??
					//Get the Encoding used by the writer. We're getting the WindowsCodePage encoding, 
					//which may not work with all languages
					Encoding enc = Encoding.GetEncoding(writer.Encoding.WindowsCodePage);

					//Get the preamble (byte-order mark) for our encoding
					byte[] preamble = enc.GetPreamble();
					int preambleLength = preamble.Length;

					//Convert the writer contents to a byte array
					byte[] body = enc.GetBytes(writer.ToString());

					//Prepend the preamble to body (store result in resized preamble array)
					Array.Resize<byte>(ref preamble, preambleLength + body.Length);
					Array.Copy(body, 0, preamble, preambleLength, body.Length);

					//Return the combined byte array
					return preamble;
				}
			}
			catch (Exception ex) {
				LogUtil.EventLogMessage(ex.ToString());
				this.GeneratorError(4, ex.ToString(), 1, 1);
				//Returning null signifies that generation has failed
				return null;
			}
		}

		private string GetDotNailsCommonNamespace(EnvDTE.Project project) {
			return project.Name + "." + "dotNailsCommon";
		}

	}
}
